Empresas
Empregos
  • Sobre nós
  • Soluções
    • Publicação de vagas
      Publique sua vaga e receba candidatos qualificados em 48h.
    • Avaliações de candidatos
      Mais de 500 testes técnicos e psicológicos, mais anti-fraude.
    • Headhunting
      Busca executiva personalizada do início ao fim.
    • Folha de Pagamento + EOR
      Dispersão de folha e EOR em mais de 15 países da LATAM.
  • Preços
  • Empregos

0

360
Visualizações
TaskCompletionSource throws "An attempt was made to transition a task to a final state when it had already completed"

I want to use TaskCompletionSource to wrap MyService which is a simple service:

public static Task<string> ProcessAsync(MyService service, int parameter)
{
    var tcs = new TaskCompletionSource<string>();
    //Every time ProccessAsync is called this assigns to Completed!
    service.Completed += (sender, e)=>{ tcs.SetResult(e.Result); };   
    service.RunAsync(parameter);
    return tcs.Task;
}

This code is working well for the first time. But the second time I call ProcessAsync simply the event handler for the Completed is assign again (the same service variable is used every time) and thus it will execute twice! and the second time it throws this exception:

attempt transition task final state when already completed

I'm not sure, should I declare the tcs as a class level variable like this:

TaskCompletionSource<string> tcs;

public static Task<string> ProccessAsync(MyService service, int parameter)
{
    tcs = new TaskCompletionSource<string>();
    service.Completed -= completedHandler; 
    service.Completed += completedHandler;
    return tcs.Task;    
}

private void completedHandler(object sender, CustomEventArg e)
{
    tcs.SetResult(e.Result); 
}

I have to wrap many methods with different return types and this way I have to write lost of code, variables, event handlers so I'm not sure if this is the best practice in this scenarios. So is there any better way of doing this job?

over 4 years ago · Santiago Trujillo
3 Respostas
Responde à pergunta

0

The issue here is that the Completed event is raised on each action but the TaskCompletionSource can only be completed once.

You can still use a local TaskCompletionSource (and you should). You just need to unregister the callback before completing the TaskCompletionSource. That way this specific callback with this specific TaskCompletionSource will never be called again:

public static Task<string> ProcessAsync(MyService service, int parameter)
{
    var tcs = new TaskCompletionSource<string>();
    EventHandler<CustomEventArg> callback = null;
    callback = (sender, e) => 
    {
        service.Completed -= callback;
        tcs.SetResult(e.Result); 
    };
    service.Completed += callback;
    service.RunAsync(parameter);
    return tcs.Task;
}

This will also solve the possible memory leak that you have when your service keeps references to all these delegates.

You should keep in mind though that you can't have multiple of these operations running concurrently. At least not unless you have a way to match requests and responses.

over 4 years ago · Santiago Trujillo Relatório

0

It appears that MyService will raise the Completed event more than once. this causes SetResult to be called more than once which causes your error.

You have 3 options that I see. Change the Completed event to only be raised once (Seems odd that you can complete more than once), change SetResult to TrySetResult so it does not throw a exception when you try to set it a 2nd time (this does introduce a small memory leak as the event still gets called and the completion source still tries to be set), or unsubscribe from the event (i3arnon's answer)

over 4 years ago · Santiago Trujillo Relatório

0

An alternative solution to i3arnon's answer would be:

public async static Task<string> ProcessAsync(MyService service, int parameter)
{
    var tcs = new TaskCompletionSource<string>();

    EventHandler<CustomEventArg> callback = 
        (s, e) => tcs.SetResult(e.Result);

    try
    {
        contacts.Completed  += callback;

        contacts.RunAsync(parameter);

        return await tcs.Task;
    }
    finally
    {
        contacts.Completed  -= callback;
    }
}

However, this solution will have a compiler generated state machine. It will use more memory and CPU.

over 4 years ago · Santiago Trujillo Relatório
Responde à pergunta
Encontrar trabalhos remotos

Descubra a nova forma de encontrar um emprego!

melhores empregos
Principais categorias de trabalho
Empresas
Postar vaga Preços Comercial
Jurídico
Termos e Condições Política de privacidade
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomende algumas ofertas para mim
Preciso de ajuda